/**
 * <AiRmob-Framework; A simple augmented reality framework for android >
    Copyright (C) <2012>  <AiRmob Team>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package airmob.framework;

import java.io.IOException;
import java.io.InputStream;

import rendercomponents.Texture;
import trackercomponents.AiRmobTracker;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.GLUtils;
import android.os.Build;
import android.os.SystemClock;

public class Pipelinehandler {
	
	AiRmobGLSurfaceView mGLSurfaceView;
	AiRmobTracker mTracker;
	public Camera mCam;
	boolean continueTracking = false;
	private android.hardware.Camera.PreviewCallback previewCallback;
	Bitmap bmp;
	public boolean sendNextFrame = false;
	public int lastTrackingRate = 0;
	

	boolean isTracking = false;
	
	// Pipelinemodes
	public static final int  MODE_VIDEOBG_RENDER_ON_TRACKED = 1;
	public static final int MODE_VIDEOBG_RENDER_CONTINUOUSLY = 2;
	public static final int MODE_TEXTUREBG_RENDER_ON_TRACKED = 3;
	public static final int MODE_FREEZE_ON_NEXT_FRAME = 4;
	
	public static final int MODE_VIDEOBG_RENDER_ON_TRACKED_WITH_BUFFER = 6;
	public static final int MODE_VIDEOBG_RENDER_CONTINUOUSLY_WITH_BUFFER = 7;
	
	public static final int MODE_STOP_TRACKING = 10;
	
	
	public static final int MODE_TEST = 5;
	/**
	 * if MODE_FREEZE_ON_NEXT_FRAME was used, this MODE will set the pipeline to the previously used MODE
	 */
	public static int MODE_BEFORE_FREEZE = 1;
	
	private int bufferSize = 2;
	
	public Pipelinehandler(AiRmobGLSurfaceView mGLSurfaceView, AiRmobTracker mTracker){
		this.mGLSurfaceView = mGLSurfaceView;
		this.mTracker = mTracker;
	}
	
	public void setBufferSize(int size){
		bufferSize = size;
	}	
	
	/**
	 * Called by AiRmobCamera.startTracking
	 * @param cam
	 */
	public void startPipeline(Camera cam){
		mCam = cam;

		
		// start the callback fitting to the selected pipelinemode
		if(MODE_BEFORE_FREEZE == MODE_VIDEOBG_RENDER_CONTINUOUSLY_WITH_BUFFER || MODE_BEFORE_FREEZE == MODE_VIDEOBG_RENDER_ON_TRACKED_WITH_BUFFER){
			int bufferByteSize = mCam.getParameters().getPreviewSize().width * mCam.getParameters().getPreviewSize().height * ImageFormat.getBitsPerPixel(ImageFormat.NV21)/8;
			for (int i = 0; i < bufferSize; i++){
				mCam.addCallbackBuffer(new byte[bufferByteSize]);				
			}
			mCam.setPreviewCallbackWithBuffer(previewCallback);
		}			
		else{
			mCam.setOneShotPreviewCallback(previewCallback);
		}
			
		

	}
	
	
	/**
	 * Sets the pipelinemode
	 * @param mode selected mode
	 */
	public void setPipelineMode(int mode){		
	
		switch (mode) {
		case MODE_VIDEOBG_RENDER_ON_TRACKED:
			setMODE_VIDEOBG_RENDER_ON_TRACKED();
			MODE_BEFORE_FREEZE = MODE_VIDEOBG_RENDER_ON_TRACKED;
			break;

		case MODE_VIDEOBG_RENDER_CONTINUOUSLY:
			setMODE_VIDEOBG_RENDER_CONTINUOUSLY();
			MODE_BEFORE_FREEZE = MODE_VIDEOBG_RENDER_CONTINUOUSLY;
			break;
			
		case MODE_TEXTUREBG_RENDER_ON_TRACKED:
			setMODE_TEXTUREBG_RENDER_ON_TRACKED();
			MODE_BEFORE_FREEZE = MODE_TEXTUREBG_RENDER_ON_TRACKED;
			break;
			
		case MODE_FREEZE_ON_NEXT_FRAME:
			setMODE_FREEZE_ON_NEXT_FRAME();
			break;
			
		case MODE_TEST:
			setMODE_TEST();
			break;
			
		case MODE_VIDEOBG_RENDER_ON_TRACKED_WITH_BUFFER:
			setMODE_VIDEOBG_RENDER_ON_TRACKED_WITH_BUFFER();
			MODE_BEFORE_FREEZE = MODE_VIDEOBG_RENDER_ON_TRACKED_WITH_BUFFER;
			break;
			
		case MODE_VIDEOBG_RENDER_CONTINUOUSLY_WITH_BUFFER:
			setMODE_VIDEOBG_RENDER_CONTINUOUSLY_WITH_BUFFER();
			MODE_BEFORE_FREEZE = MODE_VIDEOBG_RENDER_CONTINUOUSLY_WITH_BUFFER;
			break;
			
		case MODE_STOP_TRACKING:
			setMODE_STOP_TRACKING();
			break;
			
		default:
			break;
		}
	}	 
	
	
	private void setMODE_VIDEOBG_RENDER_CONTINUOUSLY_WITH_BUFFER() {
		
		
		mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
		previewCallback = null;
		previewCallback = new PreviewCallback() {
		float timeOld=0, timeNew=0;
		int counter = 0;
		
			
			public void onPreviewFrame(final byte[] data, final Camera camera) {		
				
				new Thread() {
					public void run() {
						mGLSurfaceView.mRenderer.setMatrizes(mTracker.trackImage(data));		
						camera.addCallbackBuffer(data);
					}
				}.start();				
				counter++;			
				timeNew = SystemClock.uptimeMillis();
				
				if((timeNew - timeOld) >= 1000){
					timeOld = timeNew;
					//overlayText.setText("Trackingrate: " + Integer.toString(counter) + " tps");
					lastTrackingRate = counter;
					counter = 0;
				}
			}
		};
		
	}


	private void setMODE_VIDEOBG_RENDER_ON_TRACKED_WITH_BUFFER() {
		
		mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
		previewCallback = null;
		previewCallback = new PreviewCallback() {
		float timeOld=0, timeNew=0;
		int counter = 0;
		
			
			public void onPreviewFrame(final byte[] data, final Camera camera) {		
				
				new Thread() {
					public void run() {
						mGLSurfaceView.mRenderer.setMatrizes(mTracker.trackImage(data));
						mGLSurfaceView.requestRender();
						camera.addCallbackBuffer(data);
						
						
						
						
					}
				}.start();				
				counter++;			
				timeNew = SystemClock.uptimeMillis();
				
				if((timeNew - timeOld) >= 1000){
					timeOld = timeNew;
					//overlayText.setText("Trackingrate: " + Integer.toString(counter) + " tps");
					lastTrackingRate = counter;
					counter = 0;
				}
			}
		};
		
	}


	/**
	 * The renderer will be updated for each image processed by the tracker and the videobackground is streamed directly to the display
	 */
	private void setMODE_VIDEOBG_RENDER_ON_TRACKED(){
		mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
		previewCallback = null;
		previewCallback = new PreviewCallback() {
		float timeOld=0, timeNew=0;
		int counter = 0;
		
			
			public void onPreviewFrame(final byte[] data, final Camera camera) {
				// TODO Auto-generated method stub
				
				new Thread() {
					public void run() {

						mGLSurfaceView.mRenderer.setMatrizes(mTracker.trackImage(data));
						mGLSurfaceView.requestRender();

						if (continueTracking)
							mCam.setOneShotPreviewCallback(previewCallback);
						
						
						
					}
				}.start();				
				counter++;			
				timeNew = SystemClock.uptimeMillis();
				
				if((timeNew - timeOld) >= 1000){
					timeOld = timeNew;
					//overlayText.setText("Trackingrate: " + Integer.toString(counter) + " tps");
					lastTrackingRate = counter;
					counter = 0;
				}
			}
		};
	}	
	

	
	/**
	 * The renderer will render continuously and the videobackground is streamed directly to the display
	 */
	private void setMODE_VIDEOBG_RENDER_CONTINUOUSLY(){
		mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
		previewCallback = null;
		previewCallback = new PreviewCallback() {
		float timeOld=0, timeNew=0;
		int counter = 0;
		
			
			public void onPreviewFrame(final byte[] data, final Camera camera) {
				// TODO Auto-generated method stub
				
				
				new Thread() {
					public void run() {
						isTracking = true;
						mGLSurfaceView.mRenderer.setMatrizes(mTracker.trackImage(data));						
						isTracking = false;
						if (continueTracking)
							mCam.setOneShotPreviewCallback(previewCallback);
						
					}
				}.start();				
				counter++;			
				timeNew = SystemClock.uptimeMillis();
				
				if((timeNew - timeOld) >= 1000){
					timeOld = timeNew;
					//overlayText.setText("Trackingrate: " + Integer.toString(counter) + " tps");
					lastTrackingRate = counter;
					counter = 0;
				}
			}
		};
	}	
	
	private void setMODE_TEXTUREBG_RENDER_ON_TRACKED(){
		mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
		previewCallback = null;
		previewCallback = new PreviewCallback() {
			float timeOld=0, timeNew=0;
			int counter = 0;
					
			public void onPreviewFrame(final byte[] data, final Camera camera) {
				// TODO Auto-generated method stub
				
				new Thread() {
					public void run() {
						mGLSurfaceView.mRenderer.setMatrizes(mTracker.trackImage(data));
						mGLSurfaceView.requestRender();
						if (continueTracking)
							mCam.setOneShotPreviewCallback(previewCallback);
					//	if(sendNextFrame){
						sendNextFrame = false;
						int height = camera.getParameters().getPreviewSize().height;
						int width = camera.getParameters().getPreviewSize().width;
						bmp = Bitmap.createBitmap(decodeYUV420SP(data, width, height), width, height,Bitmap.Config.ARGB_4444);
						if (bmp != null)
							mGLSurfaceView.mRenderer.setVideoBackground(bmp);
							
						
						}
						
						
					//}
				}.start();				
				counter++;			
				timeNew = SystemClock.uptimeMillis();
				
				if((timeNew - timeOld) >= 1000){
					timeOld = timeNew;
					//overlayText.setText("Trackingrate: " + Integer.toString(counter) + " tps");
					lastTrackingRate = counter;
					counter = 0;
				}
			}
		};
	}	
	
	
	private void setMODE_FREEZE_ON_NEXT_FRAME(){
		mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
		previewCallback = null;
		previewCallback = new PreviewCallback() {
					
			public void onPreviewFrame(final byte[] data, final Camera camera) {
				// TODO Auto-generated method stub
				
				new Thread() {
					public void run() {
						mGLSurfaceView.mRenderer.setMatrizes(mTracker.trackImage(data));	
						if(Build.VERSION.SDK_INT < 11){
						int height = camera.getParameters().getPreviewSize().height;
						int width = camera.getParameters().getPreviewSize().width;
						bmp = Bitmap.createBitmap(decodeYUV420SP(data, width, height), width, height,Bitmap.Config.ARGB_4444);
						if (bmp != null)
							mGLSurfaceView.mRenderer.setVideoBackground(bmp);
						}
						mCam.stopPreview();						
					}
				}.start();				
				
			}
		};
	}
	
	
	/**
	 * Testmode
	 */
	private void setMODE_TEST(){
		
		//mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
		mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
		previewCallback = null;
		
		previewCallback = new PreviewCallback() {
		float timeOld=0, timeNew=0;
		float time1=0, time2=0;
		int counter = 0;
		
		
			public void onPreviewFrame(final byte[] data, final Camera camera) {
				// TODO Auto-generated method stub
				time2 = SystemClock.uptimeMillis();

				
					
				new Thread() {
					public void run() {
						isTracking = true;
						mGLSurfaceView.mRenderer.setMatrizes(mTracker.trackImage(data));
						counter++;
						//mGLSurfaceView.requestRender();
						camera.addCallbackBuffer(data);
						isTracking = false;
						
					}
				}.start();				
					
				
				timeNew = SystemClock.uptimeMillis();
				
				if((timeNew - timeOld) >= 1000){
					timeOld = timeNew;
					//overlayText.setText("Trackingrate: " + Integer.toString(counter) + " tps");
					lastTrackingRate = counter;
					counter = 0;
				}
				
			}
		};
	}	
	
	
	/**
	 * Causes the pipeline to run out, without doing anything on the last frame. Stops tracking.
	 */
	public void setMODE_STOP_TRACKING(){
		previewCallback = null;
		previewCallback = new PreviewCallback() {			
			@Override
			public void onPreviewFrame(byte[] data, Camera camera) {
				
			}
		};
		mCam.setOneShotPreviewCallback(previewCallback);
	}
	
	
	public int[] decodeYUV420SP( byte[] yuv420sp, int width, int height) {  

	    final int frameSize = width * height;   

	    int rgb[]=new int[width*height];   
	    for (int j = 0, yp = 0; j < height; j++) {   
	        int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;   
	        for (int i = 0; i < width; i++, yp++) {   
	            int y = (0xff & ((int) yuv420sp[yp])) - 16;   
	            if (y < 0) y = 0;   
	            if ((i & 1) == 0) {   
	                v = (0xff & yuv420sp[uvp++]) - 128;   
	                u = (0xff & yuv420sp[uvp++]) - 128;   
	            }   

	            int y1192 = 1192 * y;   
	            int r = (y1192 + 1634 * v);   
	            int g = (y1192 - 833 * v - 400 * u);   
	            int b = (y1192 + 2066 * u);   

	            if (r < 0) r = 0; else if (r > 262143) r = 262143;   
	            if (g < 0) g = 0; else if (g > 262143) g = 262143;   
	            if (b < 0) b = 0; else if (b > 262143) b = 262143;   

	            rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) &    
	            		0xff00) | ((b >> 10) & 0xff);   


	        }   
	    }   
	    return rgb;   
	    }
	
}
